#light
// ---------------------------------------------------------------

namespace Langexplr.Abc

open Langexplr.Abc
open System.IO

type MethodOptionInfo = 
   { count : int; detail : (int * byte) array }
  with
     static member ReadFrom(input : BinaryReader) =
        let count = int(TypeEncodingUtilities.ReadU30Value(input))
         in 
           { count = count;
             detail = [| for _ in 1 .. count -> (int(TypeEncodingUtilities.ReadU30Value(input)), input.ReadByte()) |] }
     member this.WriteTo(output : BinaryWriter) =
        TypeEncodingUtilities.WriteU30Value(uint32(this.count),output);
        Array.iter(fun (i:int,b:byte) -> TypeEncodingUtilities.WriteU30Value(uint32(i),output);output.Write(b)) this.detail
end


type AbcMethodInfo = 
  {    paramCount : int
       returnType : int
       paramType : int array
       name : int
       flags : byte
       options : MethodOptionInfo 
       paramNames : int array }
 with
   member this.MethodName(cpool : ConstantPoolInfo) =
      if this.name = 0 then  "<zero>" else cpool.NameForIndex(this.name)
   
   static member ReadFrom( input : BinaryReader) = 
       let pCount = int(TypeEncodingUtilities.ReadU30Value(input))   
       let rType = int(TypeEncodingUtilities.ReadU30Value(input))   
       let pTypes = [|for _ in 1 .. pCount -> int(TypeEncodingUtilities.ReadU30Value(input))|]
       let aName = int(TypeEncodingUtilities.ReadU30Value(input))
       let flgs = input.ReadByte()
         in
             {
                paramCount = pCount
                returnType = rType
                paramType = pTypes
                name = aName
                flags = flgs
                options = if (flgs &&& byte(0x08) <> byte(0)) then  MethodOptionInfo.ReadFrom(input) else { count = 0 ; detail = [||] }
                paramNames = if (flgs &&& byte(0x80) <> byte(0)) then [|for _ in 1 .. pCount ->int(TypeEncodingUtilities.ReadU30Value(input)) |] else [||]
             }
             
   member this.WriteTo(output : BinaryWriter) =
      TypeEncodingUtilities.WriteU30Value(uint32(this.paramCount),output);
      TypeEncodingUtilities.WriteU30Value(uint32(this.returnType),output);
      Array.iter (fun (x:int) -> TypeEncodingUtilities.WriteU30Value(uint32(x),output)) this.paramType
      TypeEncodingUtilities.WriteU30Value(uint32(this.name),output);
      output.Write(byte(0)) // TODO WRITE THE APPROPRIATE FLAGS
      if (this.options.count <> 0) then this.options.WriteTo(output) else ();
      Array.iter (fun (x:int) -> TypeEncodingUtilities.WriteU30Value(uint32(x),output)) this.paramNames
      
      
end

type AbcMetadataItemInfo =
 {
    key : int;
    value : int
 }
with
   static member ReadFrom(input : BinaryReader) =
     { key = int(TypeEncodingUtilities.ReadU30Value(input));
       value = int(TypeEncodingUtilities.ReadU30Value(input)) }
   member this.WriteTo(output : BinaryWriter) =
      TypeEncodingUtilities.WriteU30Value(uint32(this.key),output);
      TypeEncodingUtilities.WriteU30Value(uint32(this.value),output)
end

type AbcMetadata =
 { name : int;
   itemCount : int;
   items : AbcMetadataItemInfo array }
with
   static member ReadFrom(input : BinaryReader) =
      let name = int(TypeEncodingUtilities.ReadU30Value(input))
        in
          let itemCount = int(TypeEncodingUtilities.ReadU30Value(input))
            in
               { name = name;
                 itemCount = itemCount;
                 items = [| for _ in 1 .. itemCount -> AbcMetadataItemInfo.ReadFrom(input) |] }
                 
   member this.WriteTo(output : BinaryWriter) =   
      TypeEncodingUtilities.WriteU30Value(uint32(this.name),output);
      TypeEncodingUtilities.WriteU30Value(uint32(this.itemCount),output);
      Array.iter (fun (i:AbcMetadataItemInfo) -> i.WriteTo(output)) this.items
end

type ClassFlags =
     | Sealed = 0x01
     | Final = 0x02
     | Interface = 0x04
     | ProtectedNs = 0x08

type AbcInstanceInfo = 
 {  name : int;
    superName : int;
    flags : byte;
    protectedNs : int;
    intrfCount : int;
    interfaces : int array;
    iInit : int;
    traitCount : int;
    traits : AbcTraitsInfo array }
with
   member this.HasProtectedNs  
     with get() = (this.flags &&& byte(ClassFlags.ProtectedNs) <> byte(0))
   
   static member ReadFrom(input : BinaryReader) =
      let name = int(TypeEncodingUtilities.ReadU30Value(input) ) in
      let superName = int(TypeEncodingUtilities.ReadU30Value(input) ) in
      let flags = input.ReadByte() in
      let protectedNs = if (flags &&& byte(ClassFlags.ProtectedNs) <> byte(0)) then int(TypeEncodingUtilities.ReadU30Value(input) ) else 0 in
      let intrfCount = int(TypeEncodingUtilities.ReadU30Value(input) ) in
      let interfaces = [| for _ in 1 .. intrfCount -> int(TypeEncodingUtilities.ReadU30Value(input) )|] in
      let iInit = int(TypeEncodingUtilities.ReadU30Value(input) ) in      
      let traitCount = int(TypeEncodingUtilities.ReadU30Value(input) ) in
      let traits = [| for _ in 1 .. traitCount -> AbcTraitsInfo.ReadFrom(input) |] in
      {
        name = name;
        superName = superName;
        flags = flags;
        protectedNs = protectedNs;
        intrfCount = intrfCount;
        interfaces = interfaces;
        iInit = iInit;
        traitCount = traitCount ;
        traits = traits;        
      }
      
   member this.WriteTo(output : BinaryWriter) =
      TypeEncodingUtilities.WriteU30Value(uint32(this.name),output);
      TypeEncodingUtilities.WriteU30Value(uint32(this.superName),output);
      output.Write(this.flags);
      if (this.protectedNs <> 0) then TypeEncodingUtilities.WriteU30Value(uint32(this.protectedNs),output) else ();
      TypeEncodingUtilities.WriteU30Value(uint32(this.intrfCount),output);
      Array.iter (fun (interfaceIndex:int) -> TypeEncodingUtilities.WriteU30Value(uint32(interfaceIndex),output))  this.interfaces;
      TypeEncodingUtilities.WriteU30Value(uint32(this.iInit),output);
      TypeEncodingUtilities.WriteU30Value(uint32(this.traitCount),output);
      Array.iter (fun (t:AbcTraitsInfo) -> t.WriteTo(output))  this.traits;
           
      
end

type AbcClassInfo =
 {
   cInit : int;   
   traits : AbcTraitsInfo array     
 }
with
   static member ReadFrom(input : BinaryReader) =
      let cInit = int(TypeEncodingUtilities.ReadU30Value(input))
      let traitCount = int(TypeEncodingUtilities.ReadU30Value(input))
      let traits = [|for _ in 1 .. traitCount -> AbcTraitsInfo.ReadFrom(input) |]
      in
      {
         cInit = cInit;
         traits = traits
      }
   member this.WriteTo(output : BinaryWriter) =
      TypeEncodingUtilities.WriteU30Value(uint32(this.cInit),output);
      TypeEncodingUtilities.WriteU30Value(uint32(this.traits.Length),output);
      Array.iter (fun (at:AbcTraitsInfo) -> at.WriteTo(output)) this.traits
      
end

type AbcScriptInfo =
 {
   init : int;   
   traits : AbcTraitsInfo array     
 }
with
   member this.InitMethod with get() = this.init
   static member ReadFrom(input : BinaryReader) =
      let init = int(TypeEncodingUtilities.ReadU30Value(input))
      let traitCount = int(TypeEncodingUtilities.ReadU30Value(input))
      let traits = [|for _ in 1 .. traitCount -> AbcTraitsInfo.ReadFrom(input) |]
      in
      {
         init = init;
         traits = traits
      }
      
   member this.WriteTo(output : BinaryWriter) =
      TypeEncodingUtilities.WriteU30Value(uint32(this.init),output);
      TypeEncodingUtilities.WriteU30Value(uint32(this.traits.Length),output);
      Array.iter (fun (at:AbcTraitsInfo) -> at.WriteTo(output)) this.traits
end


type AbcFile(  minorVersion : uint16,
               majorVersion : uint16,
               cPoolInfo : ConstantPoolInfo ,
               methods : AbcMethodInfo array,
               metadata : AbcMetadata array,
               instances : AbcInstanceInfo array,
               classes : AbcClassInfo array,
               scripts : AbcScriptInfo array,
               methodBodies : AbcMethodBodyInfo array) = 
  class  

   member this.ConstantPool 
     with get() = cPoolInfo
     
   member this.Scripts
     with get() = scripts

   member this.Methods
     with get() = methods

   member this.Classes
     with get() = classes
   
   member this.Instances
     with get() = instances
   
   member this.MethodBodies
     with get() = methodBodies
   
   member this.Metadata
     with get() = metadata
     
   static member ReadFrom(input : BinaryReader) =        
       let minorVersion = input.ReadUInt16() in
       let majorVersion = input.ReadUInt16() in
       let cPoolInfo = ConstantPoolInfo.ReadFrom(input) in
       let methodCount = int(TypeEncodingUtilities.ReadU30Value(input)) in
       let methods = [|for _ in 1 .. methodCount -> AbcMethodInfo.ReadFrom(input)|]  in
       let metadataCount = int(TypeEncodingUtilities.ReadU30Value(input))  in
       let metadata = [|for _ in 1 .. metadataCount -> AbcMetadata.ReadFrom(input)|]
       let classCount = int(TypeEncodingUtilities.ReadU30Value(input)) in
       let instances = [|for _ in 1 .. classCount -> AbcInstanceInfo.ReadFrom(input) |] in   
       let classes = [|for _ in 1 .. classCount -> AbcClassInfo.ReadFrom(input)|]    
       let scriptCount = int(TypeEncodingUtilities.ReadU30Value(input))  in
       let scripts = [|for _ in 1 .. scriptCount -> AbcScriptInfo.ReadFrom(input) |] in
       let methodBodyCount =  int(TypeEncodingUtilities.ReadU30Value(input)) in
       let methodBodies = [| for _ in 1 .. methodBodyCount -> AbcMethodBodyInfo.ReadFrom(input) |] in
       AbcFile(minorVersion,majorVersion,cPoolInfo,methods,metadata,instances,classes,scripts,methodBodies)
           
       
   member this.WriteTo(output : BinaryWriter) =
       output.Write(minorVersion );
       output.Write(majorVersion );
       cPoolInfo.WriteTo(output);
       TypeEncodingUtilities.WriteU30Value(uint32(methods.Length),output);
       Array.iter (fun (m:AbcMethodInfo) -> m.WriteTo(output)) methods;
       TypeEncodingUtilities.WriteU30Value(uint32(metadata.Length),output);
       Array.iter (fun (m:AbcMetadata) -> m.WriteTo(output)) metadata;
       TypeEncodingUtilities.WriteU30Value(uint32(instances.Length),output);
       Array.iter (fun (m:AbcInstanceInfo) -> m.WriteTo(output)) instances;
       Array.iter (fun (m:AbcClassInfo) -> m.WriteTo(output)) classes;
       TypeEncodingUtilities.WriteU30Value(uint32(scripts.Length),output);
       Array.iter (fun (m:AbcScriptInfo) -> m.WriteTo(output)) scripts;
       TypeEncodingUtilities.WriteU30Value(uint32(methodBodies.Length),output);
       Array.iter (fun (m:AbcMethodBodyInfo) -> m.WriteTo(output)) methodBodies;
       
  
end

            
